
#pragma once
#include <string>
#include <map>
#include <stdexcept>

namespace config {

	struct ConfigFileEx : public std::exception {
		ConfigFileEx( const std::string & s ) : std::exception( s.c_str() ){}
	};

	class ParamNotFound : public ConfigFileEx {
	public:
		ParamNotFound( const std::string & s ) : ConfigFileEx("parameter not found") {}
	};

	class ConfigNotFound : public ConfigFileEx {
	public:
		ConfigNotFound( const std::string & s ) : ConfigFileEx("config file not found") {}
	};

	class InvalidValueFormat : public ConfigFileEx {
	public:
		InvalidValueFormat( const std::string & s ): ConfigFileEx("invalid format of value"){}
	};

	template<typename T>
	struct Converter {
		T operator()( const std::string & s )
		{
			return static_cast<T>(s.c_str());
		}
	};

	template<>
	struct Converter<int> {
		int operator()( const std::string & s )
		{
			return atoi( s.c_str() );
		}
	};

	template<>
	struct Converter<unsigned> {
		int operator()( const std::string & s )
		{
			return static_cast<unsigned>( atol( s.c_str() ) );
		}
	};

	template<>
	struct Converter<unsigned short> {
		int operator()( const std::string & s )
		{
			return static_cast<unsigned short>( atol( s.c_str() ) );
		}
	};

	template<>
	struct Converter<bool> {
		bool operator()( const std::string & s )
		{
#pragma warning( push )
#pragma warning( disable : 4996 )
			bool res;
			bool setflag = false;
			const char* q = s.c_str();
			if( stricmp( q, "yes" ) == 0 
				|| stricmp( q, "1" ) == 0
				|| stricmp( q, "true" ) == 0
				|| stricmp( q, "y" ) == 0 )
			{
				res = true;
				setflag = true;
			}
			else if ( stricmp( q, "no" ) == 0
				|| stricmp( q, "0" )	 == 0
				|| stricmp( q, "false" ) == 0
				|| stricmp( q, "n" )	 == 0
				|| stricmp( q, "not" )	 == 0 )
			{
				res = false;
				setflag = true;
			}
#pragma warning( pop ) 
			if( !setflag )
			{
				throw InvalidValueFormat( s );
			}
			return res;
		}
	};



	class ConfigFile {
		typedef std::map<std::string, std::string> ParamsTable;
		ParamsTable mParams;

		void parseLine( const std::string & s );
	public:
		ConfigFile();
		ConfigFile( const std::string filename );
		~ConfigFile();

		typedef ParamsTable::iterator iterator;
		typedef ParamsTable::const_iterator const_iterator;

		void read( const std::string &filename );
		iterator begin();
		const_iterator begin() const;
		iterator end();
		const_iterator end() const;



		template<typename T, typename Conv>
		void get_value( const std::string & param, T* pval, const T& defval, Conv conv );

		template<typename T, typename Conv>
		void get_value_checked( const std::string & param, T* pval, Conv conv );

		template<typename T>
		void get_value( const std::string &param, T* pval, const T& defval )
		{
			get_value( param, pval, defval, Converter<T>() );
		}

		template<typename T>
		void get_value_checked( const std::string &param, T* pval )
		{
			get_value_checked( param, pval, Converter<T>() );
		}

		void get_value(const std::string &param, std::string *pval, const std::string &defval);

	};

	template<typename T, typename Conv>
	inline void ConfigFile::get_value( const std::string & param, T* pval, const T& defval, Conv conv )
	{
		ParamsTable::iterator it = mParams.find( param );
		if ( it != mParams.end() ){
			*pval = conv( it->second );
		} else {
			*pval = defval;
		}
	}
	
	template<typename T, typename Conv>
	inline void ConfigFile::get_value_checked( const std::string & param, T* pval, Conv conv )
	{
		ParamsTable::iterator it = mParams.find( param );
		if ( it != mParams.end() ){
			*pval = conv( it->second );
		} else {
			throw ParamNotFound( param );
		}
	}

}

